package com.polaris.live.common

import assertk.assertions.contains
import assertk.assertions.containsOnly
import assertk.assertions.isEqualTo
import assertk.assertions.isInstanceOf
import assertk.assertions.isNotNull
import com.polaris.live.common.util.ReflexUtils
import org.junit.Test
import kotlin.reflect.full.memberProperties

/**
 * ReflexUtilsTest
 *
 * @author Created by lucas on 2023/10/19 11:33
 */
class ReflexUtilsTest {

    private val testBean2 = TestBean2("bean2", 18)
    private val testBean1 = TestBean1(
        true,
        'a',
        1.toByte(),
        2,
        3,
        4,
        5.toFloat(),
        6.0,
        "string",

        testBean2,

        booleanArrayOf(true, false),
        charArrayOf('a', 'b'),
        byteArrayOf(1.toByte(), 2.toByte()),
        shortArrayOf(3, 4),
        intArrayOf(5, 6),
        longArrayOf(7, 8),
        floatArrayOf(9.toFloat(), 10.toFloat()),
        doubleArrayOf(11.0, 12.0),

        arrayOf("string1", "string2"),

        listOf("list1", "list2"),
        mapOf("key1" to "value1", "key2" to "value2"),
        listOf(testBean2, testBean2),
        mapOf("key1" to testBean2, "key2" to testBean2)
    )

    private val testMap2 = mapOf("name" to "bean2", "age" to 18)
    private val testMap1 = mapOf(
        "boolean" to true,
        "char" to 'a',
        "byte" to 1.toByte(),
        "short" to 2.toShort(),
        "int" to 3,
        "long" to 4L,
        "float" to 5.toFloat(),
        "double" to 6.0,
        "string" to "string",

        "bean" to testMap2,

        "booleanArray" to booleanArrayOf(true, false),
        "charArray" to charArrayOf('a', 'b'),
        "byteArray" to byteArrayOf(1.toByte(), 2.toByte()),
        "shortArray" to shortArrayOf(3, 4),
        "intArray" to intArrayOf(5, 6),
        "longArray" to longArrayOf(7, 8),
        "floatArray" to floatArrayOf(9.toFloat(), 10.toFloat()),
        "doubleArray" to doubleArrayOf(11.0, 12.0),

        "stringArray" to arrayOf("string1", "string2"),

        "list" to listOf("list1", "list2"),
        "map" to mapOf("key1" to "value1", "key2" to "value2"),
        "beanList" to listOf(testMap2, testMap2),
        "beanMap" to mapOf("key1" to testMap2, "key2" to testMap2)
    )

    @Test
    fun testToMap() {
        val map2 = ReflexUtils.toMap(testBean2)

        map2["name"].toAssertThat()
            .isEqualTo("bean2")
        map2["age"].toAssertThat()
            .isEqualTo(18)

        val map = ReflexUtils.toMap(testBean1)

        map.toAssertThat()
            .apply {
                contains("boolean", true)
                contains("char", 'a')
                contains("byte", 1.toByte())
                contains("short", 2.toShort())
                contains("int", 3)
                contains("long", 4L)
                contains("float", 5.toFloat())
                contains("double", 6.0)
                contains("string", "string")
                contains("bean", map2)
            }

        assertArray<BooleanArray, Boolean>(map["booleanArray"], true, false) {
            it.toTypedArray()
        }
        assertArray<CharArray, Char>(map["charArray"], 'a', 'b') {
            it.toTypedArray()
        }
        assertArray<ByteArray, Byte>(map["byteArray"], 1.toByte(), 2.toByte()) {
            it.toTypedArray()
        }
        assertArray<ShortArray, Short>(map["shortArray"], 3, 4) {
            it.toTypedArray()
        }
        assertArray<IntArray, Int>(map["intArray"], 5, 6) {
            it.toTypedArray()
        }
        assertArray<LongArray, Long>(map["longArray"], 7, 8) {
            it.toTypedArray()
        }
        assertArray<FloatArray, Float>(map["floatArray"], 9.toFloat(), 10.toFloat()) {
            it.toTypedArray()
        }
        assertArray<DoubleArray, Double>(map["doubleArray"], 11.0, 12.0) {
            it.toTypedArray()
        }

        map["stringArray"].toAssertThat()
            .isNotNull()
            .isInstanceOf<Array<Any>>()
            .containsOnly("string1", "string2")

        map["list"].toAssertThat()
            .isNotNull()
            .isInstanceOf<List<String>>()
            .containsOnly("list1", "list2")
        map["map"].toAssertThat()
            .isNotNull()
            .isInstanceOf<Map<String, String>>()
            .containsOnly(
                "key1" to "value1",
                "key2" to "value2"
            )

        map["beanList"].toAssertThat()
            .isNotNull()
            .isInstanceOf<List<Map<String, Any>>>()
            .containsOnly(map2, map2)
        map["beanMap"].toAssertThat()
            .isNotNull()
            .isInstanceOf<Map<String, Map<String, Any>>>()
            .containsOnly(
                "key1" to map2,
                "key2" to map2
            )
    }

    private inline fun <reified T : Any, reified I : Any> assertArray(
        obj: Any?,
        vararg array: I,
        block: (T) -> Array<I>
    ) {
        obj.toAssertThat()
            .isNotNull()
            .isInstanceOf<T>()
            .given {
                block(it).toAssertThat()
                    .containsOnly(*array)
            }
    }

    @Test
    fun testFromMap() {
        ReflexUtils.fromMap<TestBean2>(testMap2).toAssertThat()
            .isEqualTo(testBean2)

        val value = ReflexUtils.fromMap<TestBean1>(testMap1)
        value.toAssertThat()
            .isNotNull()
            .given { v ->
                TestBean1::class.java.isPrimitive
                TestBean1::class.memberProperties.forEach {
                    val src = it.get(v)
                    val dest = it.get(testBean1)

                    if (src is BooleanArray && dest is BooleanArray) {
                        src.toTypedArray().toAssertThat()
                            .containsOnly(*dest.toTypedArray())
                    } else if (src is CharArray && dest is CharArray) {
                        src.toTypedArray().toAssertThat()
                            .containsOnly(*dest.toTypedArray())
                    } else if (src is ByteArray && dest is ByteArray) {
                        src.toTypedArray().toAssertThat()
                            .containsOnly(*dest.toTypedArray())
                    } else if (src is ShortArray && dest is ShortArray) {
                        src.toTypedArray().toAssertThat()
                            .containsOnly(*dest.toTypedArray())
                    } else if (src is IntArray && dest is IntArray) {
                        src.toTypedArray().toAssertThat()
                            .containsOnly(*dest.toTypedArray())
                    } else if (src is LongArray && dest is LongArray) {
                        src.toTypedArray().toAssertThat()
                            .containsOnly(*dest.toTypedArray())
                    } else if (src is FloatArray && dest is FloatArray) {
                        src.toTypedArray().toAssertThat()
                            .containsOnly(*dest.toTypedArray())
                    } else if (src is DoubleArray && dest is DoubleArray) {
                        src.toTypedArray().toAssertThat()
                            .containsOnly(*dest.toTypedArray())
                    } else if (src is Array<*> && dest is Array<*>) {
                        src.toAssertThat()
                            .containsOnly(*dest)
                    } else {
                        src.toAssertThat()
                            .isNotNull()
                            .isEqualTo(dest)
                    }
                }
            }
    }
}

data class TestBean1(
    val boolean: Boolean,
    val char: Char,
    val byte: Byte,
    val short: Short,
    val int: Int,
    val long: Long,
    val float: Float,
    val double: Double,
    val string: String,
    val bean: TestBean2,

    val booleanArray: BooleanArray,
    val charArray: CharArray,
    val byteArray: ByteArray,
    val shortArray: ShortArray,
    val intArray: IntArray,
    val longArray: LongArray,
    val floatArray: FloatArray,
    val doubleArray: DoubleArray,
    val stringArray: Array<String>,

    val list: List<String>,
    val map: Map<String, String>,
    val beanList: List<TestBean2>,
    val beanMap: Map<String, TestBean2>
)

data class TestBean2(
    val name: String,
    val age: Int
)